package org.hong.monkey.crawl

import com.alibaba.fastjson.{JSONArray, JSON, JSONObject}

import scala.collection.mutable.HashMap
import java.util.ArrayList
import java.lang.management.ManagementFactory._
import java.net.NetworkInterface._
import java.net.Inet4Address
import java.util.Comparator
import org.zeromq.ZMQ.{Socket, Poller}
import org.zeromq.ZMQ
import ProcessRouter.MessageStruct._
import org.apache.commons.codec.binary.Base64

import org.hong.monkey.Logging

import scala.concurrent.ExecutionContext

/**
 * Created by open on 2016/12/12.
 */
private[monkey]
class WebkitDownloader extends HttpDownloader with Logging{

  val kDefaultTimeout = 30

  override def get(crawlRequest: CrawlRequest)(implicit executor: ExecutionContext): CrawlResponse = {

    try {
      val clientID = getClientID()
      val socket = WebkitUtils.context.socket(ZMQ.REQ)
      val poller = new ZMQ.Poller(1)
      val protocol = WebkitUtils.getProtocolFromGroup(crawlRequest.spiderkitGroup)

      if (protocol != null && !protocol.isEmpty())
        socket.connect(protocol)

      val packageBuilder = MessagePackage.newBuilder()
      packageBuilder.setName(CommandGetFreePid.Builder.getDescriptor().getFullName)

      val commandBuilder = CommandGetFreePid.newBuilder()
      commandBuilder.setClientid(clientID)
      commandBuilder.setIsStatic(false)
      packageBuilder.setCommandGetFreePid(commandBuilder)
      val pkg = packageBuilder.build()
      socket.send(pkg.toByteArray(), 0)

      poller.register(socket, ZMQ.Poller.POLLIN)
      poller.poll(kDefaultTimeout)

      var wkpid = -1;
      if (poller.pollin(0)) {
        val pid = CommandGetFreePid.parseFrom(socket.recv())
        wkpid = pid.getFreepid()
      }

      val timeout = 60000
      //TODO: check driver param and throw error
      val driver = new WebkitDriver(clientID, protocol, wkpid, socket, poller)
      var args = Array("0", if (crawlRequest.link == null || crawlRequest.link.isEmpty()) "about:blank" else crawlRequest.link,
        if (crawlRequest.cookies == null || crawlRequest.cookies == "") "[]" else crawlRequest.cookies,
        "100", String.valueOf(timeout))
      val remsg = call(driver, WebkitFunctions.WEBKIT_LONG_GET_LONG_REF_STRING_URL, args, timeout + 5000)
      val repage = toPageValue(remsg)

      driver.pageid = repage._1


      val url = getUrl(driver)
      val html = getPageSource(driver)
      val cookies = getCookies(driver)

      new CrawlResponse(crawlRequest.link, cookies, null, 200, null,
        html.getBytes(), crawlRequest.proxy, crawlRequest.carryData)
    } catch {
      case e: Throwable =>
        new CrawlResponse(crawlRequest.link, "", null, -40,
          new MonkeyDownloaderException(e), null, crawlRequest.proxy, crawlRequest.carryData)
    }
  }

  private def getPageSource(driver: WebkitDriver):String = {
    val args = Array(driver.pageid.toString())
    val remsg = call(driver, WebkitFunctions.WEBPAGE_STRING_GETPAGESOURCE_LONG_REF, args, kDefaultTimeout)
    toStringValue(remsg)
  }

  private def getUrl(driver:WebkitDriver):String = {
    val args = Array(driver.pageid.toString())
    val remsg = call(driver, WebkitFunctions.WEBPAGE_STRING_GETURL_LONG_REF, args, kDefaultTimeout)
    toStringValue(remsg)
  }

  private def getCookies(driver: WebkitDriver):String = {
    val args = Array(driver.pageid.toString())
    val remsg = call(driver, WebkitFunctions.WEBPAGE_STRING_COOKIES_LONG_REF, args, kDefaultTimeout)
    val objs = toObjectSeqValue(remsg)
    val cookies = for (i <- 0 until objs.size()) yield {
      objs.getJSONObject(i).getString("name") + "=" + objs.getJSONObject(i).getString("value")
    }
    cookies.mkString(";")
  }

  def call(driver:WebkitDriver, funcid:Long, args:Array[String], timeout:Int): String = {
    if (driver.isFailed)
      driver.reconnect()

    var requestObject:JSONObject = new JSONObject()
    requestObject.put("clientid", driver.clientID)
    requestObject.put("pageid", String.valueOf(driver.pageid))
    requestObject.put("funcid", funcid)

    for (i <- 0 until args.size)
      requestObject.put("arg"+i, encode(args(i)))

    val requestString = requestObject.toString()
    val packageBuilder = MessagePackage.newBuilder()
    packageBuilder.setName(RPCRequest.Builder.getDescriptor().getFullName())

    val requestBuilder = RPCRequest.newBuilder()
    requestBuilder.setClientid(driver.clientID)
    requestBuilder.setAimpid(driver.wkpid)
    requestBuilder.setArgs(requestString)

    packageBuilder.setRequest(requestBuilder)
    val pkg = packageBuilder.build()
    driver.socket.send(pkg.toByteArray(), 0)
    if (driver.poller.poll(timeout) > 0) {
      val answer = RPCAnswer.parseFrom(driver.socket.recv())
      if (answer.getException()) "" else answer.getRetval()
    } else {
      driver.isFailed = true
      return ""
    }
  }

  def encode(str:String): String = {
    val encodebase64 = Base64.encodeBase64(str.getBytes("UTF-8"))
    new String(encodebase64)
  }

  def decode(str:String): String = {
    new String(Base64.decodeBase64(str))
  }

  def toStringValue(remsg:String): String = {
    try {
      val reobj = JSON.parseObject(remsg)
      decode(reobj.getString("retval"))
    } catch {
      case _:Throwable => ""
    }
  }

  def toStringValueWithoutDecode(remsg:String): String = {
    try {
      val reobj = JSON.parseObject(remsg)
      reobj.getString("retval")
    } catch {
      case _:Throwable => ""
    }
  }

  def toPageValue(remsg:String):(Long, Long) = {
    try {
      val reobj = JSON.parseObject(toStringValue(remsg))
      (reobj.getString("page").toLong, reobj.getString("error").toLong)
    } catch {
      case _:Throwable => (0, -10)
    }
  }

  def toLongValue(remsg:String):Long = {
    try {
      toStringValue(remsg).toLong
    } catch {
      case _:Throwable => -1
    }
  }

  def toLongSeqValue(remsg:String):Seq[Long] = {
    try {
      val str = toStringValue(remsg)
      val obj = JSON.parseObject(str)
      val list = obj.getJSONArray("array")
      for (i <- 0 until list.size()) yield list.getString(i).toLong
    } catch {
      case _:Throwable => Seq()
    }
  }

  def toObjectSeqValue(remsg:String):JSONArray = {
    try {
      val str = toStringValue(remsg)
      JSON.parseArray(str)
    } catch {
      case _:Throwable => new JSONArray()
    }
  }

  def toRectValue(remsg:String):(Int, Int, Int, Int) = {
    try {
      val str = toStringValue(remsg)
      val obj = JSON.parseObject(str)
      (obj.getIntValue("x"), obj.getIntValue("y"), obj.getIntValue("width"), obj.getIntValue("height"))
    } catch {
      case _:Throwable => (0,0,0,0)
    }
  }

  private def getClientID(): String = {
    var ipmaps = new HashMap[String,String]()
    var iplist = new ArrayList[String]()
    try {
      var netifs = getNetworkInterfaces()
      while(netifs.hasMoreElements()) {
        val netif = netifs.nextElement()
        val name = netif.getName.toLowerCase()
        if (name.startsWith("en") ||
          name.startsWith("eth") ||
          name.startsWith("wlan") ||
          name.startsWith("ra")) {

          val ips = netif.getInetAddresses()
          while (ips.hasMoreElements()) {
            val inetaddr = ips.nextElement()
            if (inetaddr.isInstanceOf[Inet4Address]) {
              if (inetaddr.getHostAddress().startsWith("127") == false) {
                val ip = inetaddr.getHostAddress()
                ipmaps.put(name, ip)
                iplist.add(name)
              }
            }
          }
        }
      }
    } catch {
      case e:Throwable => e.printStackTrace()
    }

    iplist.sort(new Comparator[String]() {
      def compare(a:String, b:String):Int = {
        a.compareToIgnoreCase(b)
      }
    })

    val ip = if (iplist.size() > 0) {
      ipmaps.get(iplist.get(0)).get
    }

    val runtime = getRuntimeMXBean()
    val info = runtime.getName()
    val pos = info.indexOf("@")
    val pid = info.substring(0, pos)
    val tid = Thread.currentThread().getId()
    ip + ":" + pid + "/" + tid
  }
}
